/*
 * Copyright (C) 2020, Inria
 * GRAPHDECO research group, https://team.inria.fr/graphdeco
 * All rights reserved.
 *
 * This software is free for non-commercial, research and evaluation use 
 * under the terms of the LICENSE.md file.
 *
 * For inquiries contact sibr@inria.fr and/or George.Drettakis@inria.fr
 */


#pragma once

#include "Config.hpp"
#include <opencv2/opencv.hpp>
#include <functional>
#include "FFmpegVideoEncoder.hpp"

namespace sibr {
	
	SIBR_VIDEO_EXPORT std::vector<cv::Mat> cvSplitChannels(cv::Mat mat);

	template<typename T, uint N>
	struct CV_Assign {
		static void assignValue(uint c, const T & val, cv::Vec<T, N> & vec) {
			vec[c] = val;
		}
	};
	template<typename T>
	struct CV_Assign<T,1> {
		static void assignValue(uint c, const T & val, T & vec) {
			vec = val;
		}
	};

	template<typename T, uint N>
	cv::Mat cvConvertMatTo(cv::Mat mat, double scale = 1.0) {
		if (mat.type() == getOpenCVtype<T, N>) {
			return mat;
		}

		cv::Mat m, out;
		const int nc = mat.channels();
		if (nc == N) {
			m = mat;
		} else  {
			std::vector<cv::Mat> in_channels = cvSplitChannels(mat), out_channels(N);
			for (int i = 0; i < N; ++i) {
				 in_channels[i < nc ? i : (nc - 1)].copyTo(out_channels[i]);
			}
			cv::merge(out_channels, m);
		}

		m.convertTo(out, getOpenCVtype<T, N>, scale);
		return out;
	}

	template<typename T, uint N = 3>
	class VideoVolume;

	using Volume3f = VideoVolume<float, 3>;
	using Volume1f = VideoVolume<float, 1>;
	using Volume4u = VideoVolume<uchar, 4>;
	using Volume3u = VideoVolume<uchar, 3>;
	using Volume1u = VideoVolume<uchar, 1>;

	/**
	* \addtogroup sibr_video
	* @{
	*/
	template<typename T, uint N>
	class VideoVolume {
	public:
		using CVpixel = std::conditional_t<N == 1, T, cv::Vec<T, N>>;
		static const uint cv_type = getOpenCVtype<T, N>;

		// data
		int w = 0, h = 0, l = 0;
		cv::Mat_<T> mat;

		// medthods
		VideoVolume() {}

		VideoVolume(int _l, int _w, int _h) : l(_l), w(_w), h(_h) {
			mat = cv::Mat_<T>(l, w*h*N);
		}

		VideoVolume(int _l, int _w, int _h, double value) : l(_l), w(_w), h(_h) {
			mat = cv::Mat_<T>(l, w*h*N, static_cast<T>(value));
		}

		VideoVolume(cv::Mat other_volume, int _w, int _h) : w(_w), h(_h), l(other_volume.rows) {
			if (other_volume.channels() * other_volume.cols * other_volume.rows != l*w*h*N) {
				SIBR_ERR << l << " " << w << " " << h << " " << N << " : " << 
					other_volume.channels()  << " " << other_volume.rows << " " << other_volume.cols << std::endl;
			}
			mat = other_volume;
		}

		VideoVolume(const VideoVolume & other) : l(other.l) , w(other.w), h(other.h) {
			mat = other.mat;
		}

		template<typename U, uint M>
		void setupFrom(const VideoVolume<U, M> & other) {
			*this = VideoVolume(other.l, other.w, other.h);
		}

		VideoVolume clone() const {
			return VideoVolume(mat.clone(), w, h);
		}

		template<typename U, uint M = N>
		VideoVolume<U, M> convertTo() const {
			VideoVolume<U, M> out(l, w, h);
			for (int f = 0; f < l; ++f) {
				cvConvertMatTo<U, M>(frame(f)).copyTo(out.frame(f));
			}
			return out;
		}

		void toggle(double d = 255) {
			mat = d - mat;
		}

		void shift(double d) {
			mat += d;
		}
		void scale(double d) {
			mat *= d;
		}
		template<typename U>
		void add(const VideoVolume<U,N> & other) {
			cv::add(mat, other.mat, mat, cv::noArray(), cv_type);
		}

		template<typename U>
		void substract(const VideoVolume<U, N> & other) {
			cv::subtract(mat, other.mat, mat, cv::noArray(), cv_type);
		}

		template<typename U, uint M>
		void multiply(const VideoVolume<U,M> & other) {
			for (int t = 0; t < l; ++t) {
				cv::multiply(cvConvertMatTo<float, N>(frame(t)), cvConvertMatTo<float, N>(other.frame(t)), frame(t));
			}
		}

		VideoVolume concat(const VideoVolume & other) const {
			VideoVolume out(l + other.l, w, h);
			mat.copyTo(out.mat.rowRange(0, l));
			other.mat.copyTo(out.mat.rowRange(l, l + other.l));
			return out;
		}

		VideoVolume concatH(const VideoVolume & other) const {
			VideoVolume out(l, w + other.w, h);
#pragma omp parallel for
			for (int t = 0; t < l; ++t) {
				cv::hconcat(std::vector<cv::Mat_<CVpixel>>{frame(t), other.frame(t)}, out.frame(t));
			}
			return out;
		}

		VideoVolume applyMaskBinary(const Volume1u & mask) {
			VideoVolume out = VideoVolume(l, w, h, 0);
			for (int t = 0; t < l; ++t) {
				frame(t).copyTo(out.frame(t), mask.frame(t));
			}
			return  out;
		}	

		VideoVolume swapRBchannels() const {
			static_assert(N >= 3, "need 3 channels");
			VideoVolume out = VideoVolume(l, w, h, 0);

#pragma omp parallel for
			for (int t = 0; t < l; ++t) {
				cv::cvtColor(frame(t), out.frame(t), cv::COLOR_BGR2RGB);
			}
			return  out;
		}

		template<uint M>
		VideoVolume applyMask(const VideoVolume<uchar,M> & mask) {
			VideoVolume out = VideoVolume(l, w, h);
			for (int t = 0; t < l; ++t) {
				cv::multiply(cvConvertMatTo<float, N>(frame(t)), cvConvertMatTo<float, N>(mask.frame(t)), out.frame(t), 1 / 255.0);
			}
			return out;
		}

		template<typename U, uint M>
		void applyMaskInPlace(const VideoVolume<U, M> & mask) {
			for (int t = 0; t < l; ++t) {
				cv::multiply(cvConvertMatTo<float, N>(frame(t)), cvConvertMatTo<float, N>(mask.frame(t)), frame(t), 1 / 255.0);
			}
		}

		VideoVolume cutFrames(int numBefore, int numAfter) {
			int diff = l - (numBefore + numAfter);
			assert(diff >= 0);

			VideoVolume out(diff, w, h);
			mat(cv::Rect(0, numBefore, w*h*N, diff)).copyTo(out.mat);
			return out;
		}

		VideoVolume<uchar, 3> colorMapped(int colormap, double scaling = 1.0) {
			
			int custom_colormap_size;
			/* 
			//twilight
			static const float r[] = { 0.88575015840754434f, 0.88378520195539056f, 0.88172231059285788f, 0.8795410528270573f, 0.87724880858965482f, 0.87485347508575972f, 0.87233134085124076f, 0.86970474853509816f, 0.86696015505333579f, 0.86408985081463996f, 0.86110245436899846f, 0.85798259245670372f, 0.85472593189256985f, 0.85133714570857189f, 0.84780710702577922f, 0.8441261828674842f, 0.84030420805957784f, 0.83634031809191178f, 0.83222705712934408f, 0.82796894316013536f, 0.82357429680252847f, 0.81904654677937527f, 0.81438982121143089f, 0.8095999819094809f, 0.80469164429814577f, 0.79967075421267997f, 0.79454305089231114f, 0.78931445564608915f, 0.78399101042764918f, 0.77857892008227592f, 0.77308416590170936f, 0.76751108504417864f, 0.76186907937980286f, 0.75616443584381976f, 0.75040346765406696f, 0.74459247771890169f, 0.73873771700494939f, 0.73284543645523459f, 0.72692177512829703f, 0.72097280665536778f, 0.71500403076252128f, 0.70902078134539304f, 0.7030297722540817f, 0.6970365443886174f, 0.69104641009309098f, 0.68506446154395928f, 0.67909554499882152f, 0.67314422559426212f, 0.66721479803752815f, 0.6613112930078745f, 0.65543692326454717f, 0.64959573004253479f, 0.6437910831099849f, 0.63802586828545982f, 0.6323027138710603f, 0.62662402022604591f, 0.62099193064817548f, 0.61540846411770478f, 0.60987543176093062f, 0.60439434200274855f, 0.5989665814482068f, 0.59359335696837223f, 0.58827579780555495f, 0.58301487036932409f, 0.5778116438998202f, 0.5726668948158774f, 0.56758117853861967f, 0.56255515357219343f, 0.55758940419605174f, 0.55268450589347129f, 0.54784098153018634f, 0.54305932424018233f, 0.53834015575176275f, 0.53368389147728401f, 0.529090861832473f, 0.52456151470593582f, 0.52009627392235558f, 0.5156955988596057f, 0.51135992541601927f, 0.50708969576451657f, 0.5028853540415561f, 0.49874733661356069f, 0.4946761847863938f, 0.49067224938561221f, 0.4867359599430568f, 0.4828677867260272f, 0.47906816236197386f, 0.47533752394906287f, 0.47167629518877091f, 0.46808490970531597f, 0.46456376716303932f, 0.46111326647023881f, 0.45773377230160567f, 0.45442563977552913f, 0.45118918687617743f, 0.44802470933589172f, 0.44493246854215379f, 0.44191271766696399f, 0.43896563958048396f, 0.43609138958356369f, 0.43329008867358393f, 0.43056179073057571f, 0.42790652284925834f, 0.42532423665011354f, 0.42281485675772662f, 0.42037822361396326f, 0.41801414079233629f, 0.4157223260454232f, 0.41350245743314729f, 0.41135414697304568f, 0.4092768899914751f, 0.40727018694219069f, 0.40533343789303178f, 0.40346600333905397f, 0.40166714010896104f, 0.39993606933454834f, 0.3982719152586337f, 0.39667374905665609f, 0.39514058808207631f, 0.39367135736822567f, 0.39226494876209317f, 0.39092017571994903f, 0.38963580160340855f, 0.38841053300842432f, 0.38724301459330251f, 0.38613184178892102f, 0.38507556793651387f, 0.38407269378943537f, 0.38312168084402748f, 0.38222094988570376f, 0.38136887930454161f, 0.38056380696565623f, 0.37980403744848751f, 0.37908789283110761f, 0.378413635091359f, 0.37777949753513729f, 0.37718371844251231f, 0.37662448930806297f, 0.37610001286385814f, 0.37560846919442398f, 0.37514802505380473f, 0.37471686019302231f, 0.37431313199312338f, 0.37393499330475782f, 0.3735806215098284f, 0.37324816143326384f, 0.37293578646665032f, 0.37264166757849604f, 0.37236397858465387f, 0.37210089702443822f, 0.3718506155898596f, 0.37161133234400479f, 0.37138124223736607f, 0.37115856636209105f, 0.37094151551337329f, 0.37072833279422668f, 0.37051738634484427f, 0.37030682071842685f, 0.37009487130772695f, 0.36987980329025361f, 0.36965987626565955f, 0.36943334591276228f, 0.36919847837592484f, 0.36895355306596778f, 0.36869682231895268f, 0.36842655638020444f, 0.36814101479899719f, 0.36783843696531082f, 0.36751707094367697f, 0.36717513650699446f, 0.36681085540107988f, 0.36642243251550632f, 0.36600853966739794f, 0.36556698373538982f, 0.36509579845886808f, 0.36459308890125008f, 0.36405693022088509f, 0.36348537610385145f, 0.36287643560041027f, 0.36222809558295926f, 0.36153829010998356f, 0.36080493826624654f, 0.36002681809096376f, 0.35920088560930186f, 0.35832489966617809f, 0.35739663292915563f, 0.35641381143126327f, 0.35537415306906722f, 0.35427534960663759f, 0.35311574421123737f, 0.35189248608873791f, 0.35060304441931012f, 0.34924513554955644f, 0.34781653238777782f, 0.34631507175793091f, 0.34473901574536375f, 0.34308600291572294f, 0.34135411074506483f, 0.33954168752669694f, 0.33764732090671112f, 0.33566978565015315f, 0.33360804901486002f, 0.33146154891145124f, 0.32923005203231409f, 0.3269137124539796f, 0.32451307931207785f, 0.32202882276069322f, 0.31946262395497965f, 0.31681648089023501f, 0.31409278414755532f, 0.31129434479712365f, 0.30842444457210105f, 0.30548675819945936f, 0.30248536364574252f, 0.29942483960214772f, 0.29631000388905288f, 0.29314593096985248f, 0.28993792445176608f, 0.28669151388283165f, 0.28341239797185225f, 0.28010638576975472f, 0.27677939615815589f, 0.27343739342450812f, 0.27008637749114051f, 0.26673233211995284f, 0.26338121807151404f, 0.26003895187439957f, 0.25671191651083902f, 0.25340685873736807f, 0.25012845306199383f, 0.24688226237958999f, 0.24367372557466271f, 0.24050813332295939f, 0.23739062429054825f, 0.23433055727563878f, 0.23132955273021344f, 0.2283917709422868f, 0.22552164337737857f, 0.22272706739121817f, 0.22001251100779617f, 0.21737845072382705f, 0.21482843531473683f, 0.21237411048541005f, 0.21001214221188125f, 0.2077442377448806f, 0.20558051999470117f, 0.20352007949514977f, 0.20156133764129841f, 0.19971571438603364f, 0.19794834061899208f, 0.1960826032659409f, 0.19410351363791453f, 0.19199449184606268f, 0.18975853639094634f, 0.18739228342697645f, 0.18488035509396164f, 0.18774482037046955f, 0.19049578401722037f, 0.1931548636579131f, 0.19571853588267552f, 0.19819343656336558f, 0.20058760685133747f, 0.20290365333558247f, 0.20531725273301316f, 0.20785704662965598f, 0.21052882914958676f, 0.2133313859647627f, 0.21625279838647882f, 0.21930503925136402f, 0.22247308588973624f, 0.2257539681670791f, 0.22915620278592841f, 0.23266299920501882f, 0.23627495835774248f, 0.23999586188690308f, 0.24381149720247919f, 0.24772092990501099f, 0.25172899728289466f, 0.25582135547481771f, 0.25999463887892144f, 0.26425512207060942f, 0.26859095948172862f, 0.27299701518897301f, 0.27747150809142801f, 0.28201746297366942f, 0.28662309235899847f, 0.29128515387578635f, 0.2960004726065818f, 0.30077276812918691f, 0.30559226007249934f, 0.31045520848595526f, 0.31535870009205808f, 0.32029986557994061f, 0.32527888860401261f, 0.33029174471181438f, 0.33533353224455448f, 0.34040164359597463f, 0.34549355713871799f, 0.35060678246032478f, 0.35573889947341125f, 0.36088752387578377f, 0.36605031412464006f, 0.37122508431309342f, 0.3764103053221462f, 0.38160247377467543f, 0.38679939079544168f, 0.39199887556812907f, 0.39719876876325577f, 0.40239692379737496f, 0.40759120392688708f, 0.41277985630360303f, 0.41796105205173684f, 0.42313214269556043f, 0.42829101315789753f, 0.4334355841041439f, 0.43856378187931538f, 0.44367358645071275f, 0.44876299173174822f, 0.45383005086999889f, 0.45887288947308297f, 0.46389102840284874f, 0.46888111384598413f, 0.473841437035254f, 0.47877034239726296f, 0.48366628618847957f, 0.48852847371852987f, 0.49335504375145617f, 0.49814435462074153f, 0.50289524974970612f, 0.50760681181053691f, 0.51227835105321762f, 0.51690848800544464f, 0.52149652863229956f, 0.52604189625477482f, 0.53054420489856446f, 0.5350027976174474f, 0.53941736649199057f, 0.54378771313608565f, 0.54811370033467621f, 0.55239521572711914f, 0.55663229034969341f, 0.56082499039117173f, 0.56497343529017696f, 0.56907784784011428f, 0.57313845754107873f, 0.57715550812992045f, 0.58112932761586555f, 0.58506024396466882f, 0.58894861935544707f, 0.59279480536520257f, 0.59659918109122367f, 0.60036213010411577f, 0.60408401696732739f, 0.60776523994818654f, 0.6114062072731884f, 0.61500723236391375f, 0.61856865258877192f, 0.62209079821082613f, 0.62557416500434959f, 0.62901892016985872f, 0.63242534854210275f, 0.6357937104834237f, 0.6391243387840212f, 0.642417577481186f, 0.64567349382645434f, 0.64889230169458245f, 0.65207417290277303f, 0.65521932609327127f, 0.6583280801134499f, 0.66140037532601781f, 0.66443632469878844f, 0.66743603766369131f, 0.67039959547676198f, 0.67332725564817331f, 0.67621897924409746f, 0.67907474028157344f, 0.68189457150944521f, 0.68467850942494535f, 0.68742656435169625f, 0.6901389321505248f, 0.69281544846764931f, 0.69545608346891119f, 0.6980608153581771f, 0.70062962477242097f, 0.70316249458814151f, 0.70565951122610093f, 0.70812059568420482f, 0.7105456546582587f, 0.71293466839773467f, 0.71528760614847287f, 0.71760444908133847f, 0.71988521490549851f, 0.7221299918421461f, 0.72433865647781592f, 0.72651122900227549f, 0.72864773856716547f, 0.73074820754845171f, 0.73281270506268747f, 0.73484133598564938f, 0.73683422173585866f, 0.73879140024599266f, 0.74071301619506091f, 0.7425992159973317f, 0.74445018676570673f, 0.74626615789163442f, 0.74804739275559562f, 0.74979420547170472f, 0.75150685045891663f, 0.75318566369046569f, 0.75483105066959544f, 0.75644341577140706f, 0.75802325538455839f, 0.75957111105340058f, 0.7610876378057071f, 0.76257333554052609f, 0.76402885609288662f, 0.76545492593330511f, 0.76685228950643891f, 0.76822176599735303f, 0.7695642334401418f, 0.77088091962302474f, 0.77217257229605551f, 0.77344021829889886f, 0.77468494746063199f, 0.77590790730685699f, 0.7771103295521099f, 0.77829345807633121f, 0.77945862731506643f, 0.78060774749483774f, 0.78174180478981836f, 0.78286225264440912f, 0.78397060836414478f, 0.78506845019606841f, 0.78615737132332963f, 0.78723904108188347f, 0.78831514045623963f, 0.78938737766251943f, 0.79045776847727878f, 0.79152832843475607f, 0.79260034304237448f, 0.79367559698664958f, 0.79475585972654039f, 0.79584292379583765f, 0.79693854719951607f, 0.79804447815136637f, 0.7991624518501963f, 0.80029415389753977f, 0.80144124292560048f, 0.80260531146112946f, 0.80378792531077625f, 0.80499054790810298f, 0.80621460526927058f, 0.8074614045096935f, 0.80873219170089694f, 0.81002809466520687f, 0.81135014011763329f, 0.81269922039881493f, 0.81407611046993344f, 0.81548146627279483f, 0.81691575775055891f, 0.81837931164498223f, 0.81987230650455289f, 0.8213947205565636f, 0.82294635110428427f, 0.8245268129450285f, 0.82613549710580259f, 0.8277716072353446f, 0.82943407816481474f, 0.83112163529096306f, 0.83283277185777982f, 0.8345656905566583f, 0.83631898844737929f, 0.83809123476131964f, 0.83987839884120874f, 0.84167750766845151f, 0.84348529222933699f, 0.84529810731955113f, 0.84711195507965098f, 0.84892245563117641f, 0.85072697023178789f, 0.85251907207708444f, 0.85429219611470464f, 0.85604022314725403f, 0.85775662943504905f, 0.8594346370300241f, 0.86107117027565516f, 0.86265601051127572f, 0.86418343723941027f, 0.86564934325605325f, 0.86705314907048503f, 0.86839954695818633f, 0.86969131502613806f, 0.87093846717297507f, 0.87215331978454325f, 0.87335171360916275f, 0.87453793320260187f, 0.87571458709961403f, 0.87687848451614692f, 0.87802298436649007f, 0.87913244240792765f, 0.88019293315695812f, 0.88119169871341951f, 0.88211542489401606f, 0.88295168595448525f, 0.88369127145898041f, 0.88432713054113543f, 0.88485138159908572f, 0.88525897972630474f, 0.88554714811952384f, 0.88571155122845646f };
			static const float g[] = { 0.85000924943067835f, 0.85072940540310626f, 0.85127594077653468f, 0.85165675407495722f, 0.85187028338870274f, 0.85191526123023187f, 0.85180165478080894f, 0.85152403004797894f, 0.8510896085314068f, 0.85050391167507788f, 0.84976754857001258f, 0.84888934810281835f, 0.84787488124672816f, 0.84672735796116472f, 0.8454546229209523f, 0.84406482711037389f, 0.8425605950855084f, 0.84094796518951942f, 0.83923490627754482f, 0.83742600751395202f, 0.83552487764795436f, 0.8335364929949034f, 0.83146558694197847f, 0.82931896673505456f, 0.82709838780560663f, 0.82480781812080928f, 0.82245116226304615f, 0.82003213188702007f, 0.81755426400533426f, 0.81502089378742548f, 0.81243524735466011f, 0.8098007598713145f, 0.80711949387647486f, 0.80439408733477935f, 0.80162699008965321f, 0.79882047719583249f, 0.79597665735031009f, 0.79309746468844067f, 0.7901846863592763f, 0.78723995923452639f, 0.78426487091581187f, 0.78126088716070907f, 0.77822904973358131f, 0.77517050008066057f, 0.77208629460678091f, 0.7689774029354699f, 0.76584472131395898f, 0.76268908733890484f, 0.7595112803730375f, 0.75631202708719025f, 0.75309208756768431f, 0.74985201221941766f, 0.7465923800833657f, 0.74331376714033193f, 0.74001672160131404f, 0.73670175403699445f, 0.73336934798923203f, 0.73001995232739691f, 0.72665398759758293f, 0.7232718614323369f, 0.71987394892246725f, 0.7164606049658685f, 0.71303214646458135f, 0.70958887676997473f, 0.70613106157153982f, 0.7026589535425779f, 0.69917279302646274f, 0.69567278381629649f, 0.69215911458254054f, 0.68863194515166382f, 0.68509142218509878f, 0.68153767253065878f, 0.67797081129095405f, 0.67439093705212727f, 0.67079812302806219f, 0.66719242996142225f, 0.66357391434030388f, 0.65994260812897998f, 0.65629853981831865f, 0.65264172403146448f, 0.64897216734095264f, 0.6452898684900934f, 0.64159484119504429f, 0.63788704858847078f, 0.63416646251100506f, 0.6304330455306234f, 0.62668676251860134f, 0.62292757283835809f, 0.61915543242884641f, 0.61537028695790286f, 0.61157208822864151f, 0.607760777169989f, 0.60393630046586455f, 0.60009859503858665f, 0.59624762051353541f, 0.59238331452146575f, 0.5885055998308617f, 0.58461441100175571f, 0.58070969241098491f, 0.57679137998186081f, 0.57285941625606673f, 0.56891374572457176f, 0.5649543060909209f, 0.56098104959950301f, 0.55699392126996583f, 0.55299287158108168f, 0.54897785421888889f, 0.54494882715350401f, 0.54090574771098476f, 0.53684857765005933f, 0.53277730177130322f, 0.52869188011057411f, 0.52459228174983119f, 0.52047847653840029f, 0.51635044969688759f, 0.51220818143218516f, 0.50805166539276136f, 0.50388089053847973f, 0.49969585326377758f, 0.49549655777451179f, 0.49128300332899261f, 0.48705520251223039f, 0.48281316715123496f, 0.47855691131792805f, 0.47428645933635388f, 0.4700018340988123f, 0.46570306719930193f, 0.46139018782416635f, 0.45706323581407199f, 0.45272225034283325f, 0.44836727669277859f, 0.44399837208633719f, 0.43961558821222629f, 0.43521897612544935f, 0.43080859411413064f, 0.4263845142616835f, 0.42194680223454828f, 0.41749553747893614f, 0.41303079952477062f, 0.40855267638072096f, 0.4040612609993941f, 0.3995566498711684f, 0.39503894828283309f, 0.39050827529375831f, 0.38596474386057539f, 0.38140848555753937f, 0.37683963835219841f, 0.37225835004836849f, 0.36766477862108266f, 0.36305909736982378f, 0.35844148285875221f, 0.3538121372967869f, 0.34917126878479027f, 0.34451911410230168f, 0.33985591488818123f, 0.33518193808489577f, 0.33049741244307851f, 0.32580269697872455f, 0.3210981375964933f, 0.31638410101153364f, 0.31166098762951971f, 0.30692923551862339f, 0.30218932176507068f, 0.29744175492366276f, 0.29268709856150099f, 0.28792596437778462f, 0.28315901221182987f, 0.27838697181297761f, 0.27361063317090978f, 0.26883085667326956f, 0.26404857724525643f, 0.25926481158628106f, 0.25448043878086224f, 0.24969683475296395f, 0.24491536803550484f, 0.24013747024823828f, 0.23536470386204195f, 0.23059876218396419f, 0.22584149293287031f, 0.22109488427338303f, 0.21636111429594002f, 0.21164251793458128f, 0.20694122817889948f, 0.20226037920758122f, 0.197602942459778f, 0.19297208197842461f, 0.18837119869242164f, 0.18380392577704466f, 0.17927413271618647f, 0.17478570377561287f, 0.17034320478524959f, 0.16595129984720861f, 0.16161477763045118f, 0.15733863511152979f, 0.15312802296627787f, 0.14898820589826409f, 0.14492465359918028f, 0.1409427920655632f, 0.13704801896718169f, 0.13324562282438077f, 0.12954074251271822f, 0.12593818301005921f, 0.12244245263391232f, 0.11905764321981127f, 0.1157873496841953f, 0.11263459791730848f, 0.10960114111258401f, 0.10668879882392659f, 0.10389861387653518f, 0.10123077676403242f, 0.098684771934052201f, 0.096259385340577736f, 0.093952764840823738f, 0.091761187397303601f, 0.089682253716750038f, 0.087713250960463951f, 0.085850656889620708f, 0.08409078829085731f, 0.082429873848480689f, 0.080864153365499375f, 0.079389994802261526f, 0.078003941033788216f, 0.076702800237496066f, 0.075483675584275545f, 0.074344018028546205f, 0.073281657939897077f, 0.072294781043362205f, 0.071380106242082242f, 0.070533582926851829f, 0.069758206429106989f, 0.069053639449204451f, 0.068419855150922693f, 0.067857103814855602f, 0.067365888050555517f, 0.066935599661639394f, 0.066576186939090592f, 0.06628997924139618f, 0.066078173119395595f, 0.065933790675651943f, 0.065857918918907604f, 0.065859661233562045f, 0.065940385613778491f, 0.066085024661758446f, 0.066308573918947178f, 0.06661453200418091f, 0.066990462397868739f, 0.067444179612424215f, 0.067983271026200248f, 0.068592710553704722f, 0.069314066071660657f, 0.070321227242423623f, 0.071608304856891569f, 0.073182830649273306f, 0.075019861862143766f, 0.077102096899588329f, 0.079425730279723883f, 0.077251588468039312f, 0.075311278416787641f, 0.073606819040117955f, 0.072157781039602742f, 0.070974625252738788f, 0.070064576149984209f, 0.069435248580458964f, 0.068919592266397572f, 0.068484398797025281f, 0.06812195249816172f, 0.067830148426026665f, 0.067616330270516389f, 0.067465786362940039f, 0.067388214053092838f, 0.067382132300147474f, 0.067434730871152565f, 0.067557104388479783f, 0.06774359820987802f, 0.067985029964779953f, 0.068289851529011875f, 0.068653337909486523f, 0.069064630826035506f, 0.06953231029187984f, 0.070053855603861875f, 0.070616595622995437f, 0.071226716277922458f, 0.071883555446163511f, 0.072582969899254779f, 0.073315693214040967f, 0.074088460826808866f, 0.074899049847466703f, 0.075745336000958424f, 0.076617824336164764f, 0.077521963107537312f, 0.078456871676182177f, 0.079420997315243186f, 0.080412994737554838f, 0.081428390076546092f, 0.08246763389003825f, 0.083532434119003962f, 0.084622236191702671f, 0.085736654965126335f, 0.08687555176033529f, 0.088038974350243354f, 0.089227194362745205f, 0.090440685427697898f, 0.091679997480262732f, 0.092945198093777909f, 0.094238731263712183f, 0.09556181960083443f, 0.09691583650296684f, 0.098302320968278623f, 0.099722930314950553f, 0.10117945586419633f, 0.1026734006932461f, 0.10420644885760968f, 0.10578120994917611f, 0.1073997763055258f, 0.1090642347484701f, 0.11077667828375456f, 0.11253912421257944f, 0.11435355574622549f, 0.11622183788331528f, 0.11814571137706886f, 0.12012561256850712f, 0.12216445576414045f, 0.12426354237989065f, 0.12642401401409453f, 0.12864679022013889f, 0.13093210934893723f, 0.13328091630401023f, 0.13569380302451714f, 0.13817086581280427f, 0.14071192654913128f, 0.14331656120063752f, 0.14598463068714407f, 0.14871544765633712f, 0.15150818660835483f, 0.15436183633886777f, 0.15727540775107324f, 0.16024769309971934f, 0.16327738551419116f, 0.1663630904279047f, 0.16950338809328983f, 0.17269677158182117f, 0.17594170887918095f, 0.17923664950367169f, 0.18258004462335425f, 0.18597036007065024f, 0.18940601489760422f, 0.19288548904692518f, 0.19640737049066315f, 0.19997020971775276f, 0.20357251410079796f, 0.207212956082026f, 0.21089030138947745f, 0.21460331490206347f, 0.21835070166659282f, 0.22213124697023234f, 0.22594402043981826f, 0.22978799249179921f, 0.2336621873300741f, 0.23756535071152696f, 0.24149689191922535f, 0.24545598775548677f, 0.24944185818822678f, 0.25345365461983138f, 0.257490519876798f, 0.26155203161615281f, 0.26563755336209077f, 0.26974650525236699f, 0.27387826652410152f, 0.27803210957665631f, 0.28220778870555907f, 0.28640483614256179f, 0.29062280081258873f, 0.29486126309253047f, 0.29911962764489264f, 0.30339762792450425f, 0.30769497879760166f, 0.31201133280550686f, 0.31634634821222207f, 0.32069970535138104f, 0.32507091815606004f, 0.32945984647042675f, 0.33386622163232865f, 0.33828976326048621f, 0.34273019305341756f, 0.34718723719597999f, 0.35166052978120937f, 0.35614985523380299f, 0.36065500290840113f, 0.36517570519856757f, 0.36971170225223449f, 0.37426272710686193f, 0.37882848839337313f, 0.38340864508963057f, 0.38800301593162145f, 0.3926113126792577f, 0.39723324476747235f, 0.401868526884681f, 0.4065168468778026f, 0.41117787004519513f, 0.41585125850290111f, 0.42053672992315327f, 0.4252339389526239f, 0.42994254036133867f, 0.43466217184617112f, 0.43939245044973502f, 0.44413297780351974f, 0.44888333481548809f, 0.45364314496866825f, 0.45841199172949604f, 0.46318942799460555f, 0.46797501437948458f, 0.4727682731566229f, 0.47756871222057079f, 0.48237579130289127f, 0.48718906673415824f, 0.49200802533379656f, 0.49683212909727231f, 0.5016608471009063f, 0.50649362371287909f, 0.5113298901696085f, 0.51616892643469103f, 0.5210102658711383f, 0.52585332093451564f, 0.53069749384776732f, 0.53554217882461186f, 0.54038674910561235f, 0.54523059488426595f, 0.55007308413977274f, 0.55491335744890613f, 0.55975098052594863f, 0.56458533111166875f, 0.56941578326710418f, 0.5742417003617839f, 0.5790624629815756f, 0.58387743744557208f, 0.58868600173562435f, 0.5934875421745599f, 0.59828134277062461f, 0.60306670593147205f, 0.60784322087037024f, 0.61261029334072192f, 0.61736734400220705f, 0.62211378808451145f, 0.62684905679296699f, 0.63157258225089552f, 0.63628379372029187f, 0.64098213306749863f, 0.64566703459218766f, 0.65033793748103852f, 0.65499426549472628f, 0.65963545027564163f, 0.66426089585282289f, 0.6688700095398864f, 0.67346216702194517f, 0.67803672673971815f, 0.68259301546243389f, 0.68713033714618876f, 0.69164794791482131f, 0.69614505508308089f, 0.70062083014783982f, 0.70507438189635097f, 0.70950474978787481f, 0.7139109141951604f, 0.71829177331290062f, 0.72264614312088882f, 0.72697275518238258f, 0.73127023324078089f, 0.7355371221572935f, 0.73977184647638616f, 0.74397271817459876f, 0.7481379479992134f, 0.75226548952875261f, 0.75635314860808633f, 0.76039907199779677f, 0.76440101200982946f, 0.76835660399870176f, 0.77226338601044719f, 0.77611880236047159f, 0.77992021407650147f, 0.78366457342383888f, 0.78734936133548439f, 0.79097196777091994f, 0.79452963601550608f, 0.79801963142713928f, 0.8014392309950078f, 0.80478517909812231f, 0.80805523804261525f, 0.81124644224653542f, 0.81435544067514909f, 0.81737804041911244f, 0.82030875512181523f, 0.82314158859569164f, 0.82586857889438514f, 0.82848052823709672f, 0.83096715251272624f, 0.83331972948645461f, 0.8355302318472394f, 0.83759238071186537f, 0.83950165618540074f, 0.84125554884475906f, 0.84285224824778615f, 0.84429066717717349f, 0.84557007254559347f, 0.84668970275699273f, 0.84764891761519268f, 0.84844741572055415f, 0.84908426422893801f, 0.84955892810989209f, 0.84987174283631584f, 0.85002186115856315f };
			static const float b[] = { 0.8879736506427196f, 0.88723222096949894f, 0.88638056925514819f, 0.8854143767924102f, 0.88434120381311432f, 0.88316926967613829f, 0.88189704355001619f, 0.88053883390003362f, 0.87909766977173343f, 0.87757925784892632f, 0.87599242923439569f, 0.87434038553446281f, 0.8726282980930582f, 0.87086081657350445f, 0.86904036783694438f, 0.86716973322690072f, 0.865250882410458f, 0.86328528001070159f, 0.86127563500427884f, 0.85922399451306786f, 0.85713191328514948f, 0.85500206287010105f, 0.85283759062147024f, 0.85064441601050367f, 0.84842449296974021f, 0.84618210029578533f, 0.84392184786827984f, 0.8416486380471222f, 0.83936747464036732f, 0.8370834463093898f, 0.83480172950579679f, 0.83252816638059668f, 0.830266486168872f, 0.82802138994719998f, 0.82579737851082424f, 0.82359867586156521f, 0.82142922780433014f, 0.81929263384230377f, 0.81719217466726379f, 0.81513073920879264f, 0.81311116559949914f, 0.81113591855117928f, 0.80920618848056969f, 0.80732335380063447f, 0.80548841690679074f, 0.80370206267176914f, 0.8019646617300199f, 0.80027628545809526f, 0.79863674654537764f, 0.7970456043491897f, 0.79550271129031047f, 0.79400674021499107f, 0.79255653201306053f, 0.79115100459573173f, 0.78978892762640429f, 0.78846901316334561f, 0.78718994624696581f, 0.78595022706750484f, 0.78474835732694714f, 0.78358295593535587f, 0.78245259899346642f, 0.78135588237640097f, 0.78029141405636515f, 0.77925781820476592f, 0.77825345121025524f, 0.77727702680911992f, 0.77632748534275298f, 0.77540359142309845f, 0.7745041337932782f, 0.7736279426902245f, 0.77277386473440868f, 0.77194079697835083f, 0.77112734439057717f, 0.7703325054879735f, 0.76955552292313134f, 0.76879541714230948f, 0.76805119403344102f, 0.76732191489596169f, 0.76660663780645333f, 0.76590445660835849f, 0.76521446718174913f, 0.76453578734180083f, 0.76386719002130909f, 0.76320812763163837f, 0.76255780085924041f, 0.76191537149895305f, 0.76128000375662419f, 0.76065085571817748f, 0.76002709227883047f, 0.75940789891092741f, 0.75879242623025811f, 0.75817986436807139f, 0.75756936901859162f, 0.75696013660606487f, 0.75635120643246645f, 0.75574176474107924f, 0.7551311041857901f, 0.75451838884410671f, 0.75390276208285945f, 0.7532834105961016f, 0.75265946532566674f, 0.75203008099312696f, 0.75139443521914839f, 0.75075164989005116f, 0.75010086988227642f, 0.7494412559451894f, 0.74877193167001121f, 0.74809204459000522f, 0.74740073297543086f, 0.74669712855065784f, 0.74598030635707824f, 0.74524942637581271f, 0.74450365836708132f, 0.74374215223567086f, 0.7429640345324835f, 0.74216844571317986f, 0.74135450918099721f, 0.74052138580516735f, 0.73966820211715711f, 0.738794102296364f, 0.73789824784475078f, 0.73697977133881254f, 0.73603782546932739f, 0.73507157641157261f, 0.73408016787854391f, 0.7330627749243106f, 0.73201854033690505f, 0.73094665432902683f, 0.72984626791353258f, 0.72871656144003782f, 0.72755671317141346f, 0.72636587045135315f, 0.72514323778761092f, 0.72388798691323131f, 0.72259931993061044f, 0.72127639993530235f, 0.71991841524475775f, 0.71852454736176108f, 0.71709396919920232f, 0.71562585091587549f, 0.7141193695725726f, 0.71257368516500463f, 0.71098796522377461f, 0.70936134293478448f, 0.70769297607310577f, 0.70598200974806036f, 0.70422755780589941f, 0.7024287314570723f, 0.70058463496520773f, 0.69869434615073722f, 0.69675695810256544f, 0.69477149919380887f, 0.69273703471928827f, 0.69065253586464992f, 0.68851703379505125f, 0.68632948169606767f, 0.68408888788857214f, 0.68179411684486679f, 0.67944405399056851f, 0.67703755438090574f, 0.67457344743419545f, 0.67205052849120617f, 0.66946754331614522f, 0.66682322089824264f, 0.66411625298236909f, 0.66134526910944602f, 0.65850888806972308f, 0.65560566838453704f, 0.65263411711618635f, 0.64959272297892245f, 0.64647991652908243f, 0.64329409140765537f, 0.64003361803368586f, 0.63669675187488584f, 0.63328173520055586f, 0.62978680155026101f, 0.62621013451953023f, 0.62254988622392882f, 0.61880417410823019f, 0.61497112346096128f, 0.61104880679640927f, 0.60703532172064711f, 0.60292845431916875f, 0.5987265295935138f, 0.59442768517501066f, 0.59003011251063131f, 0.5855320765920552f, 0.58093191431832802f, 0.57622809660668717f, 0.57141871523555288f, 0.56650284911216653f, 0.56147964703993225f, 0.55634837474163779f, 0.55110853452703257f, 0.5457599924248665f, 0.54030245920406539f, 0.53473704282067103f, 0.52906500940336754f, 0.52328797535085236f, 0.51740807573979475f, 0.51142807215168951f, 0.50535164796654897f, 0.49918274588431072f, 0.49292595612342666f, 0.48658646495697461f, 0.48017007211645196f, 0.47368494725726878f, 0.46713728801395243f, 0.46053414662739794f, 0.45388335612058467f, 0.44719313715161618f, 0.44047194882050544f, 0.43372849999361113f, 0.42697404043749887f, 0.42021619665853854f, 0.41346259134143476f, 0.40672178082365834f, 0.40000214725256295f, 0.39331182532243375f, 0.38665868550105914f, 0.38005028528138707f, 0.37349382846504675f, 0.36699616136347685f, 0.36056376228111864f, 0.35420276066240958f, 0.34791888996380105f, 0.3417175669546984f, 0.33560648984600089f, 0.3295945757321303f, 0.32368100685760637f, 0.31786993834254956f, 0.31216524050888372f, 0.30657054493678321f, 0.30108922184065873f, 0.29574009929867601f, 0.29051361067988485f, 0.28541074411068496f, 0.28043398847505197f, 0.27559714652053702f, 0.27090279994325861f, 0.26634209349669508f, 0.26191675992376573f, 0.25765165093569542f, 0.2535289048041211f, 0.24954644291943817f, 0.24572497420147632f, 0.24205576625191821f, 0.23852974228695395f, 0.23517094067076993f, 0.23194647381302336f, 0.22874673279569585f, 0.22558727307410353f, 0.22243385243433622f, 0.2193005075652994f, 0.21618875376309582f, 0.21307651648984993f, 0.21387448578597812f, 0.2146562337112265f, 0.21542362939081539f, 0.21617499187076789f, 0.21690975060032436f, 0.21762721310371608f, 0.21833167885096033f, 0.21911516689288835f, 0.22000133917653536f, 0.22098759107715404f, 0.22207043213024291f, 0.22324568672294431f, 0.22451023616807558f, 0.22585960379408354f, 0.22728984778098055f, 0.22879681433956656f, 0.23037617493752832f, 0.23202360805926608f, 0.23373434258507808f, 0.23550427698321885f, 0.2373288009471749f, 0.23920260612763083f, 0.24112190491594204f, 0.24308218808684579f, 0.24507758869355967f, 0.24710443563450618f, 0.24915847093232929f, 0.25123493995942769f, 0.25332800295084507f, 0.25543478673717029f, 0.25755101595750435f, 0.25967245030364566f, 0.26179294097819672f, 0.26391006692119662f, 0.2660200572779356f, 0.26811904076941961f, 0.27020322893039511f, 0.27226772884656186f, 0.27430929404579435f, 0.27632534356790039f, 0.27831254595259397f, 0.28026769921081435f, 0.28218770540182386f, 0.2840695897279818f, 0.28591050458531014f, 0.2877077458811747f, 0.28945865397633169f, 0.29116024157313919f, 0.29281107506269488f, 0.29440901248173756f, 0.29595212005509081f, 0.29743856476285779f, 0.29886674369733968f, 0.30023519507728602f, 0.30154226437468967f, 0.30278652039631843f, 0.3039675809469457f, 0.30508479060294547f, 0.30613767928289148f, 0.30712600062348083f, 0.30804973095465449f, 0.30890905921943196f, 0.30970441249844921f, 0.31043636979038808f, 0.31110343446582983f, 0.31170911458932665f, 0.31225470169927194f, 0.31274172735821959f, 0.31317188565991266f, 0.31354553695453014f, 0.31386561956734976f, 0.314135190862664f, 0.31435662153833671f, 0.31453200120082569f, 0.3146630922831542f, 0.31475407592280041f, 0.31480767954534428f, 0.31482653406646727f, 0.31481299789187128f, 0.31477085207396532f, 0.31470295028655965f, 0.31461204226295625f, 0.31450102990914708f, 0.31437291554615371f, 0.31423043195101424f, 0.31407639883970623f, 0.3139136046337036f, 0.31374440956796529f, 0.31357126868520002f, 0.31339704333572083f, 0.31322399394183942f, 0.31305401163732732f, 0.31288922211590126f, 0.31273234839304942f, 0.31258523031121233f, 0.31244934410414688f, 0.31232652641170694f, 0.31221903291870201f, 0.31212881396435238f, 0.31205680685765741f, 0.31200463838728931f, 0.31197383273627388f, 0.31196698314912269f, 0.31198447195645718f, 0.31202765974624452f, 0.31209793953300591f, 0.31219689612063978f, 0.31232631707560987f, 0.31248673753935263f, 0.31267941819570189f, 0.31290560605819168f, 0.3131666792687211f, 0.3134643447952643f, 0.31379912926498488f, 0.31417223403606975f, 0.31458483752056837f, 0.31503813956872212f, 0.31553372323982209f, 0.3160724937230589f, 0.31665545668946665f, 0.31728380489244951f, 0.31795870784057567f, 0.31868137622277692f, 0.31945332332898302f, 0.3202754315314667f, 0.32114884306985791f, 0.32207478855218091f, 0.32305449047765694f, 0.32408913679491225f, 0.32518014084085567f, 0.32632861885644465f, 0.32753574162788762f, 0.3288027427038317f, 0.3301308728723546f, 0.33152138620958932f, 0.33297555200245399f, 0.33449469983585844f, 0.33607995965691828f, 0.3377325942005665f, 0.33945384341064017f, 0.3412449533046818f, 0.34310715173410822f, 0.34504169470809071f, 0.34704978520758401f, 0.34913260148542435f, 0.35129130890802607f, 0.35352709245374592f, 0.35584108091122535f, 0.35823439142300639f, 0.36070813602540136f, 0.36326337558360278f, 0.36590112443835765f, 0.36862236642234769f, 0.3714280448394211f, 0.37431909037543515f, 0.37729635531096678f, 0.380360657784311f, 0.38351275723852291f, 0.38675335037837993f, 0.39008308392311997f, 0.39350254000115381f, 0.39701221751773474f, 0.40061257089416885f, 0.40430398069682483f, 0.40808667584648967f, 0.41196089987122869f, 0.41592679539764366f, 0.41998440356963762f, 0.42413367909988375f, 0.42837450371258479f, 0.432706647838971f, 0.43712979856444761f, 0.44164332426364639f, 0.44624687186865436f, 0.45093985823706345f, 0.45572154742892063f, 0.46059116206904965f, 0.46554778281918402f, 0.47059039582133383f, 0.47571791879076081f, 0.48092913815357724f, 0.48622257801969754f, 0.49159667021646397f, 0.49705020621532009f, 0.50258161291269432f, 0.50818921213102985f, 0.51387124091909786f, 0.5196258425240281f, 0.52545108144834785f, 0.53134495942561433f, 0.53730535185141037f, 0.5433300863249918f, 0.54941691584603647f, 0.55556350867083815f, 0.56176745110546977f, 0.56802629178649788f, 0.57433746373459582f, 0.58069834805576737f, 0.58710626908082753f, 0.59355848909050757f, 0.60005214820435104f, 0.6065843782630862f, 0.61315221209322646f, 0.61975260637257923f, 0.62638245478933297f, 0.63303857040067113f, 0.63971766697672761f, 0.6464164243818421f, 0.65313137915422603f, 0.65985900156216504f, 0.66659570204682972f, 0.67333772009301907f, 0.68008125203631464f, 0.68682235874648545f, 0.69355697649863846f, 0.70027999028864962f, 0.70698561390212977f, 0.71367147811129228f, 0.72033299387284622f, 0.72696536998972039f, 0.73356368240541492f, 0.74012275762807056f, 0.74663719293664366f, 0.7530974636118285f, 0.7594994148789691f, 0.76583801477914104f, 0.77210610037674143f, 0.77829571667247499f, 0.78439788751383921f, 0.79039529663736285f, 0.796282666437655f, 0.80204612696863953f, 0.80766972324164554f, 0.81313419626911398f, 0.81841638963128993f, 0.82350476683173168f, 0.82838497261149613f, 0.8330486712880828f, 0.83748851001197089f, 0.84171925358069011f, 0.84575537519027078f, 0.84961373549150254f, 0.85330645352458923f, 0.85685572291039636f, 0.86027399927156634f, 0.86356595168669881f, 0.86673765046233331f, 0.86979617048190971f, 0.87274147101441557f, 0.87556785228242973f, 0.87828235285372469f, 0.88088414794024839f, 0.88336206121170946f, 0.88572538990087124f };
			 custom_colormap_size = 510;
			*/

			//inferno
			static const float r[] = { 0.001462f, 0.002267f, 0.003299f, 0.004547f, 0.006006f, 0.007676f, 0.009561f, 0.011663f, 0.013995f, 0.016561f, 0.019373f, 0.022447f, 0.025793f, 0.029432f, 0.033385f, 0.037668f, 0.042253f, 0.046915f, 0.051644f, 0.056449f, 0.061340f, 0.066331f, 0.071429f, 0.076637f, 0.081962f, 0.087411f, 0.092990f, 0.098702f, 0.104551f, 0.110536f, 0.116656f, 0.122908f, 0.129285f, 0.135778f, 0.142378f, 0.149073f, 0.155850f, 0.162689f, 0.169575f, 0.176493f, 0.183429f, 0.190367f, 0.197297f, 0.204209f, 0.211095f, 0.217949f, 0.224763f, 0.231538f, 0.238273f, 0.244967f, 0.251620f, 0.258234f, 0.264810f, 0.271347f, 0.277850f, 0.284321f, 0.290763f, 0.297178f, 0.303568f, 0.309935f, 0.316282f, 0.322610f, 0.328921f, 0.335217f, 0.341500f, 0.347771f, 0.354032f, 0.360284f, 0.366529f, 0.372768f, 0.379001f, 0.385228f, 0.391453f, 0.397674f, 0.403894f, 0.410113f, 0.416331f, 0.422549f, 0.428768f, 0.434987f, 0.441207f, 0.447428f, 0.453651f, 0.459875f, 0.466100f, 0.472328f, 0.478558f, 0.484789f, 0.491022f, 0.497257f, 0.503493f, 0.509730f, 0.515967f, 0.522206f, 0.528444f, 0.534683f, 0.540920f, 0.547157f, 0.553392f, 0.559624f, 0.565854f, 0.572081f, 0.578304f, 0.584521f, 0.590734f, 0.596940f, 0.603139f, 0.609330f, 0.615513f, 0.621685f, 0.627847f, 0.633998f, 0.640135f, 0.646260f, 0.652369f, 0.658463f, 0.664540f, 0.670599f, 0.676638f, 0.682656f, 0.688653f, 0.694627f, 0.700576f, 0.706500f, 0.712396f, 0.718264f, 0.724103f, 0.729909f, 0.735683f, 0.741423f, 0.747127f, 0.752794f, 0.758422f, 0.764010f, 0.769556f, 0.775059f, 0.780517f, 0.785929f, 0.791293f, 0.796607f, 0.801871f, 0.807082f, 0.812239f, 0.817341f, 0.822386f, 0.827372f, 0.832299f, 0.837165f, 0.841969f, 0.846709f, 0.851384f, 0.855992f, 0.860533f, 0.865006f, 0.869409f, 0.873741f, 0.878001f, 0.882188f, 0.886302f, 0.890341f, 0.894305f, 0.898192f, 0.902003f, 0.905735f, 0.909390f, 0.912966f, 0.916462f, 0.919879f, 0.923215f, 0.926470f, 0.929644f, 0.932737f, 0.935747f, 0.938675f, 0.941521f, 0.944285f, 0.946965f, 0.949562f, 0.952075f, 0.954506f, 0.956852f, 0.959114f, 0.961293f, 0.963387f, 0.965397f, 0.967322f, 0.969163f, 0.970919f, 0.972590f, 0.974176f, 0.975677f, 0.977092f, 0.978422f, 0.979666f, 0.980824f, 0.981895f, 0.982881f, 0.983779f, 0.984591f, 0.985315f, 0.985952f, 0.986502f, 0.986964f, 0.987337f, 0.987622f, 0.987819f, 0.987926f, 0.987945f, 0.987874f, 0.987714f, 0.987464f, 0.987124f, 0.986694f, 0.986175f, 0.985566f, 0.984865f, 0.984075f, 0.983196f, 0.982228f, 0.981173f, 0.980032f, 0.978806f, 0.977497f, 0.976108f, 0.974638f, 0.973088f, 0.971468f, 0.969783f, 0.968041f, 0.966243f, 0.964394f, 0.962517f, 0.960626f, 0.958720f, 0.956834f, 0.954997f, 0.953215f, 0.951546f, 0.950018f, 0.948683f, 0.947594f, 0.946809f, 0.946392f, 0.946403f, 0.946903f, 0.947937f, 0.949545f, 0.951740f, 0.954529f, 0.957896f, 0.961812f, 0.966249f, 0.971162f, 0.976511f, 0.982257f, 0.988362f };
			static const float g[] = { 0.000466f, 0.001270f, 0.002249f, 0.003392f, 0.004692f, 0.006136f, 0.007713f, 0.009417f, 0.011225f, 0.013136f, 0.015133f, 0.017199f, 0.019331f, 0.021503f, 0.023702f, 0.025921f, 0.028139f, 0.030324f, 0.032474f, 0.034569f, 0.036590f, 0.038504f, 0.040294f, 0.041905f, 0.043328f, 0.044556f, 0.045583f, 0.046402f, 0.047008f, 0.047399f, 0.047574f, 0.047536f, 0.047293f, 0.046856f, 0.046242f, 0.045468f, 0.044559f, 0.043554f, 0.042489f, 0.041402f, 0.040329f, 0.039309f, 0.038400f, 0.037632f, 0.037030f, 0.036615f, 0.036405f, 0.036405f, 0.036621f, 0.037055f, 0.037705f, 0.038571f, 0.039647f, 0.040922f, 0.042353f, 0.043933f, 0.045644f, 0.047470f, 0.049396f, 0.051407f, 0.053490f, 0.055634f, 0.057827f, 0.060060f, 0.062325f, 0.064616f, 0.066925f, 0.069247f, 0.071579f, 0.073915f, 0.076253f, 0.078591f, 0.080927f, 0.083257f, 0.085580f, 0.087896f, 0.090203f, 0.092501f, 0.094790f, 0.097069f, 0.099338f, 0.101597f, 0.103848f, 0.106089f, 0.108322f, 0.110547f, 0.112764f, 0.114974f, 0.117179f, 0.119379f, 0.121575f, 0.123769f, 0.125960f, 0.128150f, 0.130341f, 0.132534f, 0.134729f, 0.136929f, 0.139134f, 0.141346f, 0.143567f, 0.145797f, 0.148039f, 0.150294f, 0.152563f, 0.154848f, 0.157151f, 0.159474f, 0.161817f, 0.164184f, 0.166575f, 0.168992f, 0.171438f, 0.173914f, 0.176421f, 0.178962f, 0.181539f, 0.184153f, 0.186807f, 0.189501f, 0.192239f, 0.195021f, 0.197851f, 0.200728f, 0.203656f, 0.206636f, 0.209670f, 0.212759f, 0.215906f, 0.219112f, 0.222378f, 0.225706f, 0.229097f, 0.232554f, 0.236077f, 0.239667f, 0.243327f, 0.247056f, 0.250856f, 0.254728f, 0.258674f, 0.262692f, 0.266786f, 0.270954f, 0.275197f, 0.279517f, 0.283913f, 0.288385f, 0.292933f, 0.297559f, 0.302260f, 0.307038f, 0.311892f, 0.316822f, 0.321827f, 0.326906f, 0.332060f, 0.337287f, 0.342586f, 0.347957f, 0.353399f, 0.358911f, 0.364492f, 0.370140f, 0.375856f, 0.381636f, 0.387481f, 0.393389f, 0.399359f, 0.405389f, 0.411479f, 0.417627f, 0.423831f, 0.430091f, 0.436405f, 0.442772f, 0.449191f, 0.455660f, 0.462178f, 0.468744f, 0.475356f, 0.482014f, 0.488716f, 0.495462f, 0.502249f, 0.509078f, 0.515946f, 0.522853f, 0.529798f, 0.536780f, 0.543798f, 0.550850f, 0.557937f, 0.565057f, 0.572209f, 0.579392f, 0.586606f, 0.593849f, 0.601122f, 0.608422f, 0.615750f, 0.623105f, 0.630485f, 0.637890f, 0.645320f, 0.652773f, 0.660250f, 0.667748f, 0.675267f, 0.682807f, 0.690366f, 0.697944f, 0.705540f, 0.713153f, 0.720782f, 0.728427f, 0.736087f, 0.743758f, 0.751442f, 0.759135f, 0.766837f, 0.774545f, 0.782258f, 0.789974f, 0.797692f, 0.805409f, 0.813122f, 0.820825f, 0.828515f, 0.836191f, 0.843848f, 0.851476f, 0.859069f, 0.866624f, 0.874129f, 0.881569f, 0.888942f, 0.896226f, 0.903409f, 0.910473f, 0.917399f, 0.924168f, 0.930761f, 0.937159f, 0.943348f, 0.949318f, 0.955063f, 0.960587f, 0.965896f, 0.971003f, 0.975924f, 0.980678f, 0.985282f, 0.989753f, 0.994109f, 0.998364f };
			static const float b[] = { 0.013866f, 0.018570f, 0.024239f, 0.030909f, 0.038558f, 0.046836f, 0.055143f, 0.063460f, 0.071862f, 0.080282f, 0.088767f, 0.097327f, 0.105930f, 0.114621f, 0.123397f, 0.132232f, 0.141141f, 0.150164f, 0.159254f, 0.168414f, 0.177642f, 0.186962f, 0.196354f, 0.205799f, 0.215289f, 0.224813f, 0.234358f, 0.243904f, 0.253430f, 0.262912f, 0.272321f, 0.281624f, 0.290788f, 0.299776f, 0.308553f, 0.317085f, 0.325338f, 0.333277f, 0.340874f, 0.348111f, 0.354971f, 0.361447f, 0.367535f, 0.373238f, 0.378563f, 0.383522f, 0.388129f, 0.392400f, 0.396353f, 0.400007f, 0.403378f, 0.406485f, 0.409345f, 0.411976f, 0.414392f, 0.416608f, 0.418637f, 0.420491f, 0.422182f, 0.423721f, 0.425116f, 0.426377f, 0.427511f, 0.428524f, 0.429425f, 0.430217f, 0.430906f, 0.431497f, 0.431994f, 0.432400f, 0.432719f, 0.432955f, 0.433109f, 0.433183f, 0.433179f, 0.433098f, 0.432943f, 0.432714f, 0.432412f, 0.432039f, 0.431594f, 0.431080f, 0.430498f, 0.429846f, 0.429125f, 0.428334f, 0.427475f, 0.426548f, 0.425552f, 0.424488f, 0.423356f, 0.422156f, 0.420887f, 0.419549f, 0.418142f, 0.416667f, 0.415123f, 0.413511f, 0.411829f, 0.410078f, 0.408258f, 0.406369f, 0.404411f, 0.402385f, 0.400290f, 0.398125f, 0.395891f, 0.393589f, 0.391219f, 0.388781f, 0.386276f, 0.383704f, 0.381065f, 0.378359f, 0.375586f, 0.372748f, 0.369846f, 0.366879f, 0.363849f, 0.360757f, 0.357603f, 0.354388f, 0.351113f, 0.347777f, 0.344383f, 0.340931f, 0.337424f, 0.333861f, 0.330245f, 0.326576f, 0.322856f, 0.319085f, 0.315266f, 0.311399f, 0.307485f, 0.303526f, 0.299523f, 0.295477f, 0.291390f, 0.287264f, 0.283099f, 0.278898f, 0.274661f, 0.270390f, 0.266085f, 0.261750f, 0.257383f, 0.252988f, 0.248564f, 0.244113f, 0.239636f, 0.235133f, 0.230606f, 0.226055f, 0.221482f, 0.216886f, 0.212268f, 0.207628f, 0.202968f, 0.198286f, 0.193584f, 0.188860f, 0.184116f, 0.179350f, 0.174563f, 0.169755f, 0.164924f, 0.160070f, 0.155193f, 0.150292f, 0.145367f, 0.140417f, 0.135440f, 0.130438f, 0.125409f, 0.120354f, 0.115272f, 0.110164f, 0.105031f, 0.099874f, 0.094695f, 0.089499f, 0.084289f, 0.079073f, 0.073859f, 0.068659f, 0.063488f, 0.058367f, 0.053324f, 0.048392f, 0.043618f, 0.039050f, 0.034931f, 0.031409f, 0.028508f, 0.026250f, 0.024661f, 0.023770f, 0.023606f, 0.024202f, 0.025592f, 0.027814f, 0.030908f, 0.034916f, 0.039886f, 0.045581f, 0.051750f, 0.058329f, 0.065257f, 0.072489f, 0.079990f, 0.087731f, 0.095694f, 0.103863f, 0.112229f, 0.120785f, 0.129527f, 0.138453f, 0.147565f, 0.156863f, 0.166353f, 0.176037f, 0.185923f, 0.196018f, 0.206332f, 0.216877f, 0.227658f, 0.238686f, 0.249972f, 0.261534f, 0.273391f, 0.285546f, 0.298010f, 0.310820f, 0.323974f, 0.337475f, 0.351369f, 0.365627f, 0.380271f, 0.395289f, 0.410665f, 0.426373f, 0.442367f, 0.458592f, 0.474970f, 0.491426f, 0.507860f, 0.524203f, 0.540361f, 0.556275f, 0.571925f, 0.587206f, 0.602154f, 0.616760f, 0.631017f, 0.644924f };
			custom_colormap_size = 256;

			static cv::Mat custom_colormap;
			static bool first = true;

			if (first) {
				cv::Mat red(custom_colormap_size, 1, CV_32FC1, (void*)r),
					green(custom_colormap_size, 1, CV_32FC1, (void*)g),
					blue(custom_colormap_size, 1, CV_32FC1, (void*)b);

				cv::merge(std::vector<cv::Mat>{red, green, blue}, custom_colormap);
				cv::resize(custom_colormap, custom_colormap, cv::Size(1, 256));
				custom_colormap.convertTo(custom_colormap, CV_8UC3, 255.0);

				first = false;
			}	

			VideoVolume<uchar, 3> out(l, w, h);
			for (int f = 0; f < l; ++f) {
				cv::Mat ff = 4.0*(frame(f) - 128) + 128;
				//cv::applyColorMap(cvConvertMatTo<uchar, N>(ff, scaling), out.frame(f), custom_colormap);
				cvConvertMatTo<uchar, N>(ff, scaling).copyTo(out.frame(f));
			}
			return out;
		}

		void temporalBlur(float scaling = 1.0f) {
			const cv::Mat1f kernel = (scaling / 16.0f)*(cv::Mat1f(5, 1) << 1, 4, 6, 4, 1);
			cv::filter2D(mat, mat, -1, kernel, cv::Point(-1, -1), 0.0, cv::BORDER_DEFAULT);
		}

		void temporalBlurBoundaryConditions(float scaling, double left, double right) {
			const cv::Mat1f kernel = (scaling / 16.0f)*(cv::Mat1f(5, 1) << 1, 4, 6, 4, 1);

			VideoVolume tmp(l, w, h);
			for (int i = 0; i < h; ++i) {
				for (int j = 0; j < w; ++j) {
					for (int c = 0; c < N; ++c) {					
						for (int t = 0; t < l; ++t) {
							double res = 0;
							for (int dt = -2; dt <= 2; ++dt) {
								int u = t + dt;
								double w = kernel(2 + dt), val;
								if (u < 0) {
									val = left;
								} else if (u >= l) {
									val = right;
								} else {
									val = valueAt(u, i, j, c);
								}
								res += w * val;
							}
							tmp.valueAt(t, i, j, c) = cv::saturate_cast<T>(res) ;
						}
					}				
				}
			}
			std::swap(mat, tmp.mat);
		}

		void extendedTemporalBlur() {
			//const cv::Mat kernel = (1 / 5.0f)*(cv::Mat_<float>(5, 1) << 1, 1, 1, 1, 1);
			//cv::filter2D(mat, mat, -1, kernel, cv::Point(-1, -1), cv::BORDER_REPLICATE);
			cv::boxFilter(mat, mat, -1, cv::Size(1, 25), cv::Point(-1, -1), true, cv::BORDER_REPLICATE);
		}

		VideoVolume pyrDownSpacial() const {
			VideoVolume out(l, (w + 1) / 2, (h + 1) / 2);
			for (int f = 0; f < l; ++f) {
				cv::pyrDown(frame(f), out.frame(f));
			}
			return out;
		}

		VideoVolume pyrDownTemporalModif() {
			temporalBlur();
			VideoVolume out((l + 1) / 2, w, h);
			for (int f = 0; f < out.l; ++f) {
				frame(2 * f).copyTo(out.frame(f));
			}
			return out;
		}

		VideoVolume pyrDownTemporal() const {
			VideoVolume tmp(mat.clone(), w, h);
			return tmp.pyrDownTemporalModif();
		}

		VideoVolume pyrDownTemporalBox() const {
			VideoVolume tmp(l, w, h);
			const cv::Mat kernel = (1.0 / 16.0f)*(cv::Mat_<float>(5, 1) << 1, 4, 6, 4, 1);
			cv::filter2D(mat, tmp.mat, -1, kernel, cv::Point(-1, -1), 0.0, cv::BORDER_REPLICATE);
			//cv::boxFilter(mat, tmp.mat, -1, cv::Size(1, 5), { -1,-1 }, true, cv::BORDER_REPLICATE);
			VideoVolume out((l + 1) / 2, w, h);
			cv::resize(tmp.mat, out.mat, out.mat.size(), 0, 0, cv::INTER_LINEAR);
			return out;
		}

		VideoVolume extendedDownScaleTemporal() const {
			VideoVolume tmp(mat.clone(), w, h);
			tmp.extendedTemporalBlur();
			VideoVolume out((l + 1) / 2, w, h);
			for (int f = 0; f < out.l; ++f) {
				tmp.frame(2 * f).copyTo(out.frame(f));
			}
			return out;
		}

		VideoVolume pyrDown() const {
			return pyrDownSpacial().pyrDownTemporalModif();
		}

		VideoVolume pyrUpSpacial(int _w, int _h) const {
			VideoVolume out(l, _w, _h);
			for (int f = 0; f < l; ++f) {
				cv::pyrUp(frame(f), out.frame(f), cv::Size(_w, _h));
			}
			return out;
		}

		VideoVolume pyrUpTemporal(int _l) const {
			VideoVolume out(_l, w, h, 0);
			//for (int f = 0; f < l; ++f) {
			//	frame(f).copyTo(out.frame(2 * f));
			//}
			//out.temporalBlur(2.0f);
			cv::resize(mat, out.mat, out.mat.size(), 0, 0, cv::INTER_LINEAR);
			out.temporalBlur(1.0f);

			return out;
		}
		VideoVolume pyrUpTemporalBoundaryConditions(int _l, double left, double right) const {
			VideoVolume out(_l, w, h, 0);
			cv::resize(mat, out.mat, out.mat.size(), 0, 0, cv::INTER_LINEAR);
			out.temporalBlurBoundaryConditions(1.0f, left, right);

			return out;
		}

		VideoVolume pyrDownBoundaryConditions(double left, double right) const {
			VideoVolume tmp = clone();
			tmp.temporalBlurBoundaryConditions(1.0f, left, right);
			VideoVolume out((l + 1) / 2, w, h);
			cv::resize(tmp.mat, out.mat, out.mat.size(), 0, 0, cv::INTER_LINEAR);
			return out;
		}

		VideoVolume pyrUp(int _l, int _w, int _h) const {
			return pyrUpTemporal(_l).pyrUpSpacial(_w, _h);
		}

		void play(int delay = 30, const sibr::Vector2i & res = { -1,-1 }, double scale = 1.0) const {
			bool playing = true;
			int t = 0;

			const std::string win_name = "playing";
			auto disp_frame = [&] {
				if ((res.array() >= 0).all()) {
					cv::Mat m;
					cv::resize(cvConvertMatTo<uchar, 3>(frame(t), scale), m, cv::Size(res[0],res[1]), 0,0, cv::INTER_NEAREST);
					cv::imshow(win_name, m);
				} else {
					cv::imshow(win_name, cvConvertMatTo<uchar, 3>(frame(t), scale));
				}
			};
			
			auto true_cb = [&](int new_t) {
				t = new_t;
				disp_frame();
			};
			auto cb_wrapper = [](int new_t, void * arg) { (*static_cast<decltype(true_cb)*>(arg))(new_t); };

			while (playing) {					
				disp_frame(); 
				cv::createTrackbar("timestamp", win_name, &t, l - 1, cb_wrapper, &true_cb);	
				playing = (cv::waitKey(delay) != 27);
				t = (t + 1) % l;
			}

			cv::destroyWindow(win_name);
		}

		void playStd(double scale = 1.0) const {
			play(30, { 800,600 }, scale);
		}

		void saveToVideoFile(const std::string & filepath, double framerate = 30.0) const {
			Path file = filepath;
			makeDirectory(file.parent_path().string());

			sibr::FFVideoEncoder output(filepath, framerate, { w,h });
			for (int f = 0; f < l; ++f) {
				output << sibr::cvConvertMatTo<uchar,3>(frame(f));
			}
			output.close();
		}

		cv::Mat_<CVpixel> frame(int t) {
			return mat.row(t).reshape(N, h);
		}
		const cv::Mat_<CVpixel> frame(int t) const {
			return mat.row(t).reshape(N, h);
		}

		cv::Mat_<T> time_sequence(int i, int j, int c = 0) const {
			return mat.col(N*(w*i + j) + c);
		}
		cv::Mat_<CVpixel> time_sequence_pixels(int i, int j) const {
			return mat.colRange(N*(w*i + j), N*(w*i + j + 1)).reshape(N, 0);
		}

		VideoVolume time_sequence_volume(int i, int j) const {
			return VideoVolume(time_sequence_pixels(i,j).clone(), 1, 1);
		}

		cv::Mat video_line(int i) const {
			return mat.colRange(N*w*i, N*w*(i + 1));
		}

		VideoVolume subVolumeRef(int t_start, int t_end) {
			return VideoVolume(mat.rowRange(t_start, t_end), w, h);
		}

		VideoVolume subVolume(int t_start, int t_end) const {
			return VideoVolume(mat.rowRange(t_start, t_end).clone(), w, h);
		}

		VideoVolume subVolumeSpatial(int x, int y, int w, int h) const {
			VideoVolume out(l, w, h);
			cv::Rect rec(x, y, w, h);
#pragma omp parallel for
			for (int t = 0; t < l; ++t) {
				frame(t)(rec).copyTo(out.frame(t));
			}
			return out;
		}

		VideoVolume subVolume_i(int i_start, int i_end) const {
			return VideoVolume(mat.colRange(N*w*i_start, N*w*i_end), w, i_end - i_start);
		}

		VideoVolume subVolume_i_copy(int i_start, int i_end) const {
			return VideoVolume(mat.colRange(N*w*i_start, N*w*i_end).clone(), w, i_end - i_start);
		}

		VideoVolume duplicateTime(int num) const {
			VideoVolume out(l*num, w, h);
			for (int i = 0; i < num; ++i) {
				mat.copyTo(out.mat.rowRange(l*i, l*(i + 1)));
			}
			return out;
		}

		cv::Mat_<CVpixel> spatialSlice(int i_start, int j_start, int i_end, int j_end) {
			Vector2i start = { i_start, j_start }, end = { i_end, j_end };
			Vector2i diff = end - start;
			int num = diff.cwiseAbs().maxCoeff();
			
			cv::Mat_<CVpixel> out(l, num + 1);
			for (int s = 0; s <= num; ++s) {
				Vector2i p = (start.cast<float>() + (s / (float)num)*diff.cast<float>()).cast<int>();

				time_sequence_pixels(p[0], p[1]).copyTo(out.col(s));
				//for (int c = 0; c < N; ++c) {
				//	out.col(c + i * N) = time_sequence(p[0], p[1], c);
				//}
			}
			return out;
		}

		cv::Mat_<CVpixel> median_frame() const {
			cv::Mat_<CVpixel> out_median(h, w);

#pragma omp parallel for
			for (int i = 0; i < h; ++i) {
				for (int j = 0; j < w; ++j) {				
					for (int c = 0; c < N; ++c) {
						std::vector<T> vals_vec;
						time_sequence(i, j, c).copyTo(vals_vec);
						std::nth_element(vals_vec.begin(), vals_vec.begin() + vals_vec.size() / 2, vals_vec.end());
						CV_Assign<T, N>::assignValue(c, vals_vec[vals_vec.size() / 2], out_median(i, j));
					}
				}
			}
			return out_median;
		}

		T & valueAt(int t, int i, int j, int c = 0) {
			return mat(t, N*(w*i + j) + c);
		}
		const T & valueAt(int t, int i, int j, int c = 0) const {
			return mat(t, N*(w*i + j) + c);
		}

		CVpixel & pixelAt(int t, int i, int j) {
			return frame(t)(i, j);
		}
		const CVpixel & pixelAt(int t, int i, int j) const {
			return frame(t)(i, j);
		}

		bool isValid() const {
			return !mat.empty();
		}

		void cout() const {
			std::cout << l << " x " << w <<  " x " << h << std::endl;
		}

		VideoVolume spatialRescale(float f) const {
			Vector2i size = (f * Vector2f(w, h) + Vector2f(0.5f, 0.5f)).cast<int>();

			VideoVolume out(l, size[0], size[1]);
			for (int f = 0; f < l; ++f) {
				cv::resize(frame(f), out.frame(f), cv::Size(size[0], size[1]), 0, 0, cv::INTER_LINEAR);
			}
			return out;
		}


	};

	SIBR_VIDEO_EXPORT Volume3u loadVideoVolume(const std::string & filepath);
	SIBR_VIDEO_EXPORT Volume3u loadVideoVolume(sibr::Video & vid);
	
	template<typename T, uint N, uint M> 
	VideoVolume<T, N + M>  concatVolumesChannels(const sibr::VideoVolume<T, N> & A, const sibr::VideoVolume<T, M> & B) {
		VideoVolume<T, N + M> out(A.l, A.w, A.h);

#pragma omp parallel for
		for (int t = 0; t < A.l; ++t) {
			std::vector<cv::Mat> A_cs, B_cs;
			cv::split(A.frame(t), A_cs);
			cv::split(B.frame(t), B_cs);
			A_cs.insert(A_cs.end(), B_cs.begin(), B_cs.end());
			cv::merge(A_cs, out.frame(t));
		}
		return out;
	}

	SIBR_VIDEO_EXPORT uint optimal_num_levels(uint length);

	template<uint M>
	std::vector<sibr::VideoVolume<uchar, M>> extendedBlurPyramid(const sibr::VideoVolume<uchar, M> & vid, uint num_levels = 0) {
		if (num_levels == 0) {
			num_levels = optimal_num_levels(vid.l);
		}

		std::vector<sibr::VideoVolume<uchar, M>> out(1, vid);
		for (uint i = 1; i < num_levels; ++i) {
			out.push_back(out.back().extendedDownScaleTemporal());
		}
		return out;
	}

	template<uint M>
	std::vector<sibr::VideoVolume<uchar, M>> gaussianPyramid(const sibr::VideoVolume<uchar, M> & vid, uint num_levels = 0) {
		if (num_levels == 0) {
			num_levels = optimal_num_levels(vid.l);
		}

		std::vector<sibr::VideoVolume<uchar, M>> out(1, vid);
		for (uint i = 1; i < num_levels; ++i) {
			out.push_back(out.back().pyrDown());
		}
		return out;
	}

	template<typename T, uint M>
	std::vector<sibr::VideoVolume<T, M>> gaussianPyramidTemporal (const sibr::VideoVolume<T, M> & vid, uint num_levels = 0) {
		if (num_levels == 0) {
			num_levels = optimal_num_levels(vid.l);
		}

		std::vector<sibr::VideoVolume<T, M>> out(1, vid);
		for (uint i = 1; i < num_levels; ++i) {
			out.push_back(out.back().pyrDownTemporal());
		}
		return out;
	}

	template<typename T, uint M>
	std::vector<sibr::VideoVolume<T, M>> gaussianPyramidTemporalBox(const sibr::VideoVolume<T, M> & vid, uint num_levels = 0) {
		if (num_levels == 0) {
			num_levels = optimal_num_levels(vid.l);
		}

		std::vector<sibr::VideoVolume<T, M>> out(1, vid);
		for (uint i = 1; i < num_levels; ++i) {
			out.push_back(out.back().pyrDownTemporalBox());
		}
		return out;
	}

	SIBR_VIDEO_EXPORT std::vector<sibr::Volume3u> laplacianPyramid(const sibr::Volume3u & vid, uint num_levels = 0);
	//SIBR_VIDEO_EXPORT std::vector<sibr::Volume3u> laplacianPyramidTemporal(const sibr::Volume3u & vid, uint num_levels = 0);
	SIBR_VIDEO_EXPORT std::vector<sibr::Volume3u> laplacianPyramidTemporalDouble(const sibr::Volume3u & vid, uint num_levels = 0);


	template< typename U, typename T = U>
	std::vector<VideoVolume<T,3>> laplacianPyramidTemporal(const VideoVolume<U, 3>& vid, uint num_levels = 0)
	{
		if (num_levels == 0) {
			num_levels = optimal_num_levels(vid.l);
		}

		std::vector<VideoVolume<T, 3>> out;

		sibr::Volume3f current_v = vid.template convertTo<float>(), down, up;
		for (int i = 0; i < (int)num_levels - 1; ++i) {
			//std::cout << i << " " << current_v.l << std::endl;
			down = current_v.pyrDownTemporal();
			up = down.pyrUpTemporal(current_v.l);
			//current_v.play(30, { 1200,800 });
			//up.play(30, { 1200,800 });
			current_v.substract(up);
			current_v.shift(128);
			out.push_back(current_v.convertTo<T>());
			std::swap(current_v, down);
		}
		out.push_back(current_v.convertTo<T>());
		return out;
	}

	SIBR_VIDEO_EXPORT sibr::Volume3u collapseLaplacianPyramid(const std::vector<sibr::Volume3u> & pyr, double shift = 0);

	template<typename T>
	sibr::VideoVolume<T,3> collapseLaplacianPyramidTemporal(const std::vector<sibr::VideoVolume<T, 3>>& pyr, double shift,
		bool debug = false)
	{
		sibr::Volume3f v = (VideoVolume<T, 3>) pyr.back().convertTo();
		for (int i = (int)pyr.size() - 2; i >= 0; --i) {
			if (debug) {
				v.play();
			}
			v = v.pyrUpTemporal(pyr[i].l);
			if (debug) {
				v.play();
			}
			v.add(pyr[i]);
			if (shift != 0) {
				v.shift(shift);
				if (debug) {
					v.play();
				}
			}
		}
		return v.convertTo<T>();
	}

	//SIBR_VIDEO_EXPORT sibr::Volume3u collapseLaplacianPyramidTemporal(const std::vector<sibr::Volume3u> & pyr, double shift = 0);

	//SIBR_VIDEO_EXPORT sibr::Volume3u laplacianBlendingTemporal(const sibr::Volume3u & vA, const sibr::Volume3u & vB, std::vector<sibr::Volume1u> & pyrM);
	SIBR_VIDEO_EXPORT sibr::Volume3u laplacianBlending(const sibr::Volume3u & vA, const sibr::Volume3u & vB, std::vector<sibr::Volume1u> & pyrM);

	template<typename T_V, typename T_M>
	sibr::VideoVolume<T_V, 3> laplacianBlendingTemporal(
		const sibr::VideoVolume<T_V,3> & vA,
		const sibr::VideoVolume<T_V, 3> & vB,
		std::vector<sibr::VideoVolume<T_M, 1>> & pyrM)
	{
		uint num_levels = (uint)pyrM.size();

		auto pyrA = laplacianPyramidTemporal(vA, num_levels);

		for (const auto & l : pyrA) {
			//l.play();
		}

		auto pyrB = laplacianPyramidTemporal(vB, num_levels);

		//auto pyrM = gaussianPyramidTemporal(vM);

		for (int i = (int)pyrA.size() - 1; i >= 0; --i) {
			//pyrA[i].playStd();
			//pyrM[i].playStd();
			pyrA[i].applyMaskInPlace(pyrM[i]);
			//pyrA[i].playStd();
			pyrM[i].toggle();
			//pyrM[i].playStd();
			//pyrB[i].playStd();
			pyrB[i].applyMaskInPlace(pyrM[i]);
			//pyrB[i].playStd();
			pyrA[i].add(pyrB[i]);
			//pyrA[i].playStd();
		}

		return collapseLaplacianPyramidTemporal(pyrA, -128);
	}

	template<typename T, uint N, typename Pix = typename VideoVolume<float, N>::CVpixel>
	cv::Mat_<Pix> totalVariation(const VideoVolume<T, N> & v) {
		cv::Mat_<Pix> total_vars(v.h, v.w);

#pragma omp parallel for
		for (int i = 0; i < v.h; ++i) {
			for (int j = 0; j < v.w; ++j) {
				for (int c = 0; c < N; ++c) {
					double total_var = 0;
					auto seq = v.time_sequence(i, j, c).clone();
					for (int t = 0; t < v.l - 1; ++t) {
						total_var += std::abs((double)seq(t) - (double)seq(t + 1));
					}
					CV_Assign<float, N>::assignValue(c, (float)total_var, total_vars(i, j));
				}				
			}
		}

		return total_vars;
	}

	struct SIBR_VIDEO_EXPORT PyramidLayer {
		PyramidLayer() {}
		PyramidLayer(int _w, int _h, int _l, int cv_type = CV_32FC1) : l(_l), w(_w), h(_h) {
			volume = cv::Mat(l, 3 * w*h, cv_type);
		}
		PyramidLayer(const cv::Mat & _volume, int _w, int _h) : w(_w), h(_h), l(_volume.rows) {
			_volume.convertTo(volume, CV_32FC1);
		}

		PyramidLayer operator+(const PyramidLayer & other);
		PyramidLayer operator-(const PyramidLayer & other);

		PyramidLayer clone() const {
			PyramidLayer out = *this;
			volume.copyTo(out.volume);
			return out;
		}

		cv::Mat getRGB(int frame, bool centered = false);

		void saveToVideoFile(const std::string & filename, double framerate);

		void copySizeFrom(const PyramidLayer & other) {
			h = other.h;
			w = other.w;
			l = other.l;
		}

		void cout() const {
			std::cout << l << " " << w << " " << h << std::endl;
		}

		void show(int s = 50) const;
		static void show(PyramidLayer A, PyramidLayer B, int s = 50);
		static void show(PyramidLayer A, PyramidLayer B, PyramidLayer C, int s = 50);
		static void show(PyramidLayer A, PyramidLayer B, PyramidLayer C, PyramidLayer D, int s = 50, bool centered = false);
		static void showDiff(PyramidLayer A, PyramidLayer B, int s = 50);

		cv::Mat volume;
		int w, h, l;
	};


	struct SIBR_VIDEO_EXPORT PyramidParameters {
		PyramidParameters(int nlevels = 5, int temporal = 3, int spatial = 2, bool spatial_ds = true) :
			num_levels(nlevels), temporal_radius(temporal), spatial_radius(spatial), splacialDS(spatial_ds) {}
		int num_levels; 
		int temporal_radius;
		int spatial_radius;
		bool splacialDS = true;
	};

	SIBR_VIDEO_EXPORT PyramidLayer blur(const PyramidLayer & layer, const PyramidParameters &  params );
	SIBR_VIDEO_EXPORT PyramidLayer temporalBlur(const PyramidLayer & volume, const PyramidParameters & params, float scaling = 1);
	SIBR_VIDEO_EXPORT void temporalBlurInPlace(PyramidLayer & volume, const PyramidParameters & params, float scaling = 1);

	SIBR_VIDEO_EXPORT PyramidLayer decimate(const PyramidLayer & layer, const PyramidParameters &  params);
	SIBR_VIDEO_EXPORT PyramidLayer upscale(const PyramidLayer & layerUp, const PyramidLayer & layerDown, const PyramidParameters &  params);
	SIBR_VIDEO_EXPORT PyramidLayer downscale(const PyramidLayer & layer, const PyramidParameters &  params);

	SIBR_VIDEO_EXPORT cv::Mat slice(const PyramidLayer & layer, int i, int j, bool vertical = true, bool center = false);

	
	class SIBR_VIDEO_EXPORT VideoGaussianPyramid {
	public:
		PyramidParameters params;
		std::vector<PyramidLayer> layers;

	};

	class SIBR_VIDEO_EXPORT VideoLaplacianPyramid {
	public:

	public:
		VideoLaplacianPyramid() {}

	public:

		PyramidParameters params;

		PyramidLayer collapse() const;

		std::vector<PyramidLayer> layers;
		
	};

	
	SIBR_VIDEO_EXPORT VideoGaussianPyramid buildVideoGaussianPyramid(sibr::Video & vid, int nLevels, const PyramidParameters & params = {}, bool show = false);
	SIBR_VIDEO_EXPORT VideoGaussianPyramid buildVideoGaussianPyramid(const cv::Mat & volume, int w, int h, int nLevels, const PyramidParameters & params = {}, bool show = false);

	SIBR_VIDEO_EXPORT VideoLaplacianPyramid buildVideoLaplacianPyramid(PyramidLayer vid, int nLevels, const PyramidParameters & params = {}, bool show = false);
	SIBR_VIDEO_EXPORT VideoLaplacianPyramid buildVideoLaplacianPyramid(sibr::Video & vid, int nLevels, const PyramidParameters & params = {}, bool show = false);

	SIBR_VIDEO_EXPORT VideoLaplacianPyramid buildVideoLaplacianPyramidFullyReduced(PyramidLayer vid, int nLevels, const PyramidParameters & params = {}, bool show = false);
	SIBR_VIDEO_EXPORT void convertReducedVideoPyramidTo128(VideoLaplacianPyramid & vid);

	SIBR_VIDEO_EXPORT PyramidLayer videoLaplacianBlending(sibr::Video & vidA, sibr::Video & vidB, PyramidLayer mask_volume );
	SIBR_VIDEO_EXPORT PyramidLayer videoLaplacianBlending(PyramidLayer vidA, PyramidLayer vidB, PyramidLayer mask_volume, PyramidParameters params = {}, bool show = false);

	struct SIBR_VIDEO_EXPORT ContribData {
		PyramidLayer contrib, mask, partA, partB;
	};

	struct SIBR_VIDEO_EXPORT FullContribData {
		ContribData scaled;
		ContribData notScaled;
		PyramidLayer result, inputA, inputB;
	};

	SIBR_VIDEO_EXPORT std::vector<FullContribData> videoLaplacianBlendingContrib(PyramidLayer vidA, PyramidLayer vidB, PyramidLayer mask_volume, PyramidParameters params = {});

	SIBR_VIDEO_EXPORT void videoLaplacianBlendingDebug(PyramidLayer vidA, PyramidLayer vidB, PyramidLayer mask_volume, PyramidParameters params = {});

	template<typename T, uint N> 
	class Histogram {
		using Value = Vector<T, N>;
		using Indice = Vector<uint, N>;
		using Range = Vector<double, N>;

	public:
		Histogram(const Value & _min, const Value & _max, uint _numBins = 100)
			: min(_min.template cast<double>()), max(_max.template cast<double>()), numBins(_numBins) {
			Range diff = max - min;
			scaling = numBins * diff.cwiseInverse();
			bin_range = diff / numBins;
		}

		Indice whatBin(const Value & value) {
			Indice bin;
			for (int c = 0; c < N; ++c) {
				bin[c] = sibr::clamp((int)(scaling[c]*(value[c] - min[c])), 0, (int)numBins - 1);
			}
			return bin;
		}

		void addValue(const Value & value) {
			++bins[whatBin(value)];
		}
		void addValues(const std::vector<Value> & values) {
			for (const Value & v : values) {
				addValue(v);
			}
		}

		Indice getModeIndice() const {
			Indice mode;
			uint mode_size = 0;
			for (const auto & key_val : bins) {
				if (key_val.second > mode_size) {
					mode_size = key_val.second;
					mode = key_val.first;
				}
			}
			return mode;
		}

		Value getBinMiddle(const Indice & bin) const {
			Value out;
			for (int c = 0; c < 3; ++c) {
				out[c] = static_cast<T>(min[c] + bin_range[c] * (bin[c] + 0.5));
			}
			return out;
		}

	protected:
		std::map<Indice, uint> bins;
		Range max, min, scaling, bin_range;
		uint numBins = 50;
	};

	template<typename T> 
	class Histogram<T, 1> {
		SIBR_CLASS_PTR(Histogram);

	public:
		Histogram(double _min, double _max, int _numBins = 100) : min(_min), max(_max), numBins(_numBins), bins(_numBins, 0) {
			scaling = numBins / (max - min);
			bin_range = (max - min) / numBins;
		}

		bool whatBin(T value, uint & bin) {
			int t = (int)(scaling * (value - min));
			if (t >= 0 && t <= ((int)numBins - 1)) {
				bin = t;
				return true;
			}
			return false;
		}

		void addValue(T value) {
			uint bin;
			if (whatBin(value, bin)) {
				++bins[bin];
			}
		}

		void addValues(const std::vector<T> & values) {
			for (const T & v : values) {
				addValue(v);
			}
		}

		uint getModeIndice() const {
			uint mode, mode_size = 0;
			for (const auto & [key, val] : bins) {
				if (val > mode_size) {
					mode_size = val;
					mode = key;
				}
			}
			return mode;
		}

		T getBinMiddle(uint bin) const {
			return static_cast<T>(min + bin_range * (bin + 0.5));
		}

		std::vector<float> normalized_values() const {
			float sum = 0;
			for (uint b = 0; b < numBins; ++b) {
				sum += bins[b];
			}
			std::vector<float> out(numBins);
			for (uint b = 0; b < numBins; ++b) {
				out[b] = bins[b]/sum;
			}
			return out;
		}

	protected:
		std::vector<uint> bins;
		double max, min, scaling, bin_range;
		uint numBins = 50;
	};

	using Histo1f = Histogram<float, 1>;
	using ColorHistogram = Histogram<uchar, 3>;

	struct TimeHistogram {

		TimeHistogram(double _min, double _max, int _numBins) : numBins(_numBins), max(_max), min(_min) {
			assert(numBins > 0 && max > min);
			scaling = numBins / (max - min);
			bin_range = (max - min) / numBins;
		}

		void addValues(const std::vector<sibr::Vector3ub> & values) {
			for (const sibr::Vector3ub & value : values) {
				addValue(value);
			}
		}

		sibr::Vector3ub getBinMiddle(const sibr::Vector3ub & bin) const {
			sibr::Vector3ub out;
			for (int c = 0; c < 3; ++c) {
				out[c] = sibr::clamp((int)(min + bin_range * (bin[c] + 0.5)), 0, 255);
			}
			return out;
		}

		sibr::Vector3ub getHMode() const {
			sibr::Vector3ub mode;
			int mode_size = 0;
			for (const auto & key_val : bins) {
				if (key_val.second > mode_size) {
					mode_size = key_val.second;
					mode = key_val.first;
				}
			}
			return mode;
		}

		void addValue(const sibr::Vector3ub & value) {
			++bins[whatBin(value)];
		}

		sibr::Vector3ub whatBin(const sibr::Vector3ub & value) {
			sibr::Vector3ub bin;
			for (int c = 0; c < 3; ++c) {
				bin[c] = sibr::clamp((int)(scaling*(value[c] - min)), 0, numBins - 1);
			}
			return bin;
		}

		void computeSortedBins() {
			std::vector<std::pair<int, sibr::Vector3ub>> all_bins;
			int num_elts = 0;
			for (const auto & bin : bins) {
				num_elts += bin.second;
				all_bins.push_back({ bin.second,bin.first });
			}
			std::sort(all_bins.begin(), all_bins.end());
			float cdf = 0; 
			for (int i = (int)all_bins.size() - 1; i >= 0; --i) {
				float f = all_bins[i].first /(float)num_elts;
				//std::cout << "(" << all_bins[i].first << ", " << (int)all_bins[i].second[0] << "),";
				
				sorted_bins[all_bins[i].second] = cdf;
				cdf = std::min(cdf + f, 1.0f);
			}
			//std::cout << std::endl;
		}
		std::map<sibr::Vector3ub, int> bins;
		std::map<sibr::Vector3ub, float> sorted_bins;
		double max, min, scaling, bin_range;
		int numBins = 50;
	};

	class SIBR_VIDEO_EXPORT VideoUtils {

	public:
		static void simpleFlowViz(cv::VideoCapture & cap, float ratio);
		static void simpleFlowSave(cv::VideoCapture & cap, float ratio, std::function<std::string(int flow_id)> naming_f);

		template<typename FunType, typename... OtherArgsTypes>
		static void loopAndDisplay(cv::VideoCapture & cap, float ratio, FunType f, const OtherArgsTypes &... args);

		static void deepFlowViz(cv::VideoCapture & cap, float ratio);

		static cv::Mat getGrey(const cv::Mat & mat);

		static cv::Mat getFlowViz(const cv::Mat & flow);

		static cv::Mat cropFromSize(const cv::Mat & mat, const sibr::Vector2i & size);

		static void getMeanVariance(cv::VideoCapture & cap, cv::Mat & outMean, cv::Mat & outVariance, const sibr::Vector2i & finalSize);
		static void getMeanVariance2(cv::VideoCapture & cap, cv::Mat & outMean, cv::Mat & outVariance, const sibr::Vector2i & finalSize, float starting_point_s = 0);

		static cv::Mat getMedian(sibr::Video & vid, float time_skiped_begin = 0, float time_skiped_end = 0);
		static cv::Mat3b getMedian(const std::string & path, float time_percentage_crop  = 0);

		static cv::Mat getBackgroundImage(sibr::Video & vid, int numBins = 50, float time_skip_begin = 0, float time_skip_end = 0);
		static cv::Mat getBackgroundImage(const cv::Mat volume, int w, int h, int numBins = 50);
		static void getBackGroundVideo(sibr::Video & vid, PyramidLayer & out_mask, PyramidLayer & out_video, cv::Mat & mask,
			const sibr::ImageRGB & mean = {}, int threshold = 75, int numBins = 50, float time_skip_begin = 0, float time_skip_end = 0);

		static sibr::Volume1u getBackgroundVolume(const sibr::Volume3u & volume, int threshold = 75, int numBins = 150);
		static sibr::Volume1f getBackgroundVolumeF(const sibr::Volume3u & volume, int numBins = 150);

		static void computeSaveSimpleFlow(sibr::Video & vid, bool viz = false);

		static cv::Mat getTemporalSpatialRatio(sibr::Video & vid, PyramidLayer & out_ratio, const sibr::ImageRGB & spatial_ratio,
			int numBins = 50, float time_skip_begin = 0, float time_skip_end = 0);

		static cv::Mat getLaplacian(cv::Mat mat, int size = 3, bool smooth = false, bool absolute = false);

		static cv::Mat getCanny(cv::Mat mat);


		static void computeSaveVideoMaskF(Video & vid, int threshold, bool viz = false);
		static void computeSaveVideoMaskBlur(Video & vid, int time_window);

		static int rotationAngleFromMetadata(const std::string & videoPath);

		static void ECCtransform(cv::Mat matA, cv::Mat matB, cv::Mat & correctedB, cv::Mat & diff, int cvMotion);

		static void smallAlignmentVideo(sibr::Video & vid, const std::string & outputVidPath, bool viz = false);
		static void smallAlignmentVideo2(sibr::Video & vid, const std::string & outputVidPath, bool viz = false);

		static int codec_ffdshow;
		static int codec_OpenH264;
		static int codec_OpenH264_fallback;

	protected:
		static cv::Mat applyFlow(const cv::Mat & prev, const cv::Mat & flow);

		static void simpleFlow(cv::VideoCapture & cap, float ratio,
			std::function<bool(cv::Mat prev, cv::Mat next, cv::Mat flow, int flow_id)> f,
			std::function<void(void)> end_function = []() {}
		);

		static void deepFlow(cv::VideoCapture & cap, float ratio,
			std::function<bool(cv::Mat prev, cv::Mat next, cv::Mat flow, int flow_id)> f,
			std::function<void(void)> end_function = []() {}
		);
	};

	template<typename FunType, typename... OtherArgsTypes>
	void VideoUtils::loopAndDisplay(cv::VideoCapture & cap, float ratio, FunType f, const OtherArgsTypes &... args)
	{
		cv::Mat next;
		static_assert(std::is_same_v<decltype(f(next, args...)), cv::Mat>, "FunType must return cv::Mat");

		cap.set(cv::VideoCaptureProperties::CAP_PROP_POS_FRAMES, 0);

		while (true) {
			cap >> next;
			if (next.empty()) {
				break;
			}
			auto size = cv::Size((int)(ratio*next.size().width), (int)(ratio*next.size().height));
			cv::resize(next, next, size);
			cv::imshow("imshow", f(next, args...));
			if (cv::waitKey(10) == 27) {
				break;
			}
		}
		cv::destroyAllWindows();
	}

	/** }@ */

}
